/**
 * Secure WebSocket Server for Server Function Calls
 * 
 * This server provides a WebSocket endpoint that allows client applications
 * to directly call server-side JavaScript functions with SSL/TLS support.
 */
const WebSocket = require('ws');
const fs = require('fs');
const path = require('path');
const https = require('https');
require('dotenv').config();

// Enhanced logging function
function logWithDetails(message, details = null) {
  const timestamp = new Date().toISOString();
  console.log(`[${timestamp}] [WSS-SERVER] ${message}`);
  if (details) {
    console.log(JSON.stringify(details, null, 2));
  }
}

// Log startup information
logWithDetails('Starting Secure WebSocket server');

// Configuration
const WS_PORT = process.env.WS_PORT || 8080;
const SCRIPTS_DIR = process.env.SCRIPTS_DIR || path.join(process.cwd(), 'src', 'scripts');
const SSL_PREFIX = process.env.SSL_PREFIX || '/etc/letsencrypt/live/';
const DOMAIN = process.env.DOMAIN || 'sykotic1.com';

// SSL Certificate paths
const SSL_KEY_PATH = process.env.SSL_KEY_PATH || path.join(SSL_PREFIX, DOMAIN, 'privkey.pem');
const SSL_CERT_PATH = process.env.SSL_CERT_PATH || path.join(SSL_PREFIX, DOMAIN, 'fullchain.pem');

// Log SSL configuration
logWithDetails('SSL Configuration:', {
  sslKeyPath: SSL_KEY_PATH,
  sslCertPath: SSL_CERT_PATH,
  keyExists: fs.existsSync(SSL_KEY_PATH),
  certExists: fs.existsSync(SSL_CERT_PATH)
});

// Ensure scripts directory exists
if (!fs.existsSync(SCRIPTS_DIR)) {
  logWithDetails(`Creating scripts directory: ${SCRIPTS_DIR}`);
  try {
    fs.mkdirSync(SCRIPTS_DIR, { recursive: true });
  } catch (err) {
    logWithDetails(`Failed to create scripts directory:`, {
      error: err.message,
      stack: err.stack
    });
  }
}

// Track connection stats
const wsStats = {
  totalConnections: 0,
  activeConnections: 0,
  messageCount: 0,
  errorCount: 0
};

try {
  // Create HTTPS server with SSL
  const httpsServer = https.createServer({
    key: fs.readFileSync(SSL_KEY_PATH),
    cert: fs.readFileSync(SSL_CERT_PATH)
  });

  // Create WebSocket server attached to HTTPS server
  const wss = new WebSocket.Server({ 
    server: httpsServer,
    // Handle path-based routing if needed
    verifyClient: (info) => {
      const path = info.req.url;
      logWithDetails(`WebSocket connection attempt at path: ${path}`, {
        headers: info.req.headers,
        ip: info.req.socket.remoteAddress
      });
      
      // You can restrict paths if needed, for now accept all
      return true;
    }
  });

  // Log when the WebSocket server starts listening
  wss.on('listening', () => {
    logWithDetails(`Secure WebSocket server is now listening on port ${WS_PORT}`);
    logWithDetails(`Looking for method files in: ${SCRIPTS_DIR}`);
    
    // List available methods at startup
    if (fs.existsSync(SCRIPTS_DIR)) {
      const files = fs.readdirSync(SCRIPTS_DIR);
      logWithDetails(`Available methods:`, {
        scriptsDir: SCRIPTS_DIR,
        methodFiles: files
      });
    }
  });

  // Log any server-level errors
  wss.on('error', (error) => {
    wsStats.errorCount++;
    logWithDetails('WebSocket server error:', {
      error: error.message,
      stack: error.stack
    });
  });

  // Handle WebSocket connections
  wss.on('connection', (ws, req) => {
    wsStats.totalConnections++;
    wsStats.activeConnections++;
    
    const clientIp = req.socket.remoteAddress;
    const connectionId = `wss-${Date.now()}-${wsStats.totalConnections}`;
    
    logWithDetails('New WebSocket connection established', {
      connectionId,
      clientIp,
      path: req.url,
      headers: req.headers,
      stats: { ...wsStats }
    });
    
    // Log when the connection closes
    ws.on('close', (code, reason) => {
      wsStats.activeConnections--;
      logWithDetails('WebSocket connection closed', {
        connectionId,
        code,
        reason: reason.toString ? reason.toString() : reason,
        stats: { ...wsStats }
      });
    });
    
    // Log errors on this connection
    ws.on('error', (error) => {
      wsStats.errorCount++;
      logWithDetails('WebSocket connection error', {
        connectionId,
        error: error.message,
        stack: error.stack,
        stats: { ...wsStats }
      });
    });
    
    // Handle WebSocket messages
    ws.on('message', async (message) => {
      wsStats.messageCount++;
      let requestId = 'unknown';
      
      try {
        // Convert Buffer to string if needed
        const messageStr = message.toString();
        
        logWithDetails('WebSocket message received', {
          connectionId,
          messageLength: messageStr.length,
          messagePreview: messageStr.substring(0, 200)
        });
        
        const request = JSON.parse(messageStr);
        requestId = request.requestId || 'unknown';
        const { methodName, args = [] } = request;
        
        logWithDetails(`WebSocket request details:`, {
          connectionId,
          requestId,
          methodName,
          argsLength: args.length,
          args: args.map(arg => 
            typeof arg === 'object' ? '(object)' : 
            typeof arg === 'string' && arg.length > 100 ? `${arg.substring(0, 100)}...` : arg
          )
        });
        
        // Look for the server-side method in the scripts directory
        const methodPath = path.join(SCRIPTS_DIR, `${methodName}.js`);
        
        logWithDetails(`Looking for method file:`, {
          connectionId,
          requestId, 
          methodPath,
          scriptsDir: SCRIPTS_DIR,
          methodFileExists: fs.existsSync(methodPath)
        });
        
        if (!fs.existsSync(methodPath)) {
          const error = `Method file not found: ${methodName}.js`;
          logWithDetails(`Error: ${error}`, {
            connectionId,
            requestId,
            methodPath,
            scriptsDir: SCRIPTS_DIR,
            scriptsExists: fs.existsSync(SCRIPTS_DIR),
            scriptsContents: fs.existsSync(SCRIPTS_DIR) ? fs.readdirSync(SCRIPTS_DIR) : []
          });
          
          ws.send(JSON.stringify({
            requestId,
            error
          }));
          return;
        }
        
        // Clear require cache to ensure we get the latest version
        logWithDetails(`Clearing require cache for: ${methodPath}`, {
          connectionId,
          requestId
        });
        delete require.cache[require.resolve(methodPath)];
        
        // Load the method
        try {
          logWithDetails(`Loading method from file: ${methodPath}`, {
            connectionId,
            requestId
          });
          const method = require(methodPath);
          
          if (typeof method !== 'function') {
            const error = `Invalid method in ${methodName}.js: expected a function`;
            logWithDetails(`Error: ${error}`, {
              connectionId,
              requestId,
              methodType: typeof method
            });
            
            ws.send(JSON.stringify({
              requestId,
              error
            }));
            return;
          }
          
          // Execute the method
          try {
            logWithDetails(`Executing method: ${methodName}`, {
              connectionId,
              requestId
            });
            
            const result = await method(...args);
            
            logWithDetails(`Method executed successfully:`, {
              connectionId,
              requestId,
              resultPreview: typeof result === 'object' ? 
                JSON.stringify(result).substring(0, 200) : 
                String(result).substring(0, 200)
            });
            
            // Send back the result
            ws.send(JSON.stringify({
              requestId,
              result
            }));
            
            logWithDetails(`Response sent back to client`, {
              connectionId,
              requestId
            });
          } catch (error) {
            logWithDetails(`Error executing ${methodName}:`, {
              connectionId,
              requestId,
              error: error.message,
              stack: error.stack
            });
            
            ws.send(JSON.stringify({
              requestId,
              error: error.message
            }));
          }
        } catch (error) {
          logWithDetails(`Error requiring module ${methodPath}:`, {
            connectionId,
            requestId,
            error: error.message,
            stack: error.stack
          });
          
          ws.send(JSON.stringify({
            requestId,
            error: `Error loading method: ${error.message}`
          }));
        }
      } catch (error) {
        logWithDetails('WebSocket message parsing error:', {
          connectionId,
          error: error.message,
          stack: error.stack,
          rawMessage: typeof message === 'string' ? message.substring(0, 200) : 'Non-string message'
        });
        
        ws.send(JSON.stringify({
          requestId,
          error: 'Invalid message format'
        }));
      }
    });
    
    // Send a welcome message to confirm connection
    try {
      ws.send(JSON.stringify({
        type: 'system',
        message: 'Secure WebSocket connection established successfully',
        connectionId,
        timestamp: new Date().toISOString()
      }));
      
      logWithDetails('Welcome message sent to client', {
        connectionId
      });
    } catch (error) {
      logWithDetails('Error sending welcome message', {
        connectionId,
        error: error.message
      });
    }
  });

  // Start the HTTPS server
  httpsServer.listen(WS_PORT, () => {
    logWithDetails(`HTTPS server for WebSockets listening on port ${WS_PORT}`);
  });

  // Log periodic WebSocket stats
  setInterval(() => {
    if (wsStats.totalConnections > 0) {
      logWithDetails('WebSocket server stats', wsStats);
    }
  }, 60000); // Log every minute

  // Create a simple generateJwtSecret method if it doesn't exist
  const sampleMethodPath = path.join(SCRIPTS_DIR, 'generateJwtSecret.js');
  if (!fs.existsSync(sampleMethodPath)) {
    logWithDetails(`Creating sample generateJwtSecret.js method at: ${sampleMethodPath}`);
    
    try {
      const sampleMethod = `
  /**
   * Generates a secure JWT secret
   * This is a sample server method that can be called via WebSocket
   */
  const crypto = require('crypto');

  module.exports = async function generateJwtSecret() {
    // Generate a random 32-byte hex string
    const secret = crypto.randomBytes(32).toString('hex');
    
    return {
      secret,
      timestamp: new Date().toISOString(),
      generated: true
    };
  };
  `;
      
      fs.writeFileSync(sampleMethodPath, sampleMethod);
      logWithDetails('Sample method created successfully');
    } catch (error) {
      logWithDetails('Error creating sample method:', {
        error: error.message,
        stack: error.stack
      });
    }
  }

} catch (error) {
  logWithDetails('FATAL ERROR starting secure WebSocket server:', {
    error: error.message,
    stack: error.stack
  });
  
  // Inform about possible SSL issues
  if (error.code === 'ENOENT' && error.message.includes('ENOENT')) {
    logWithDetails('SSL certificate files not found. Please check the paths:', {
      keyPath: SSL_KEY_PATH,
      certPath: SSL_CERT_PATH,
      sslPrefix: SSL_PREFIX,
      domain: DOMAIN
    });
    
    logWithDetails('You can set custom paths by configuring these environment variables:', {
      SSL_PREFIX: 'Base directory for certificates (default: /etc/letsencrypt/live/)',
      DOMAIN: 'Domain name for certificate directory (default: sykotic1.com)',
      SSL_KEY_PATH: 'Full path to private key (overrides SSL_PREFIX and DOMAIN)',
      SSL_CERT_PATH: 'Full path to certificate (overrides SSL_PREFIX and DOMAIN)'
    });
  }
}

// Log uncaught exceptions
process.on('uncaughtException', (error) => {
  logWithDetails('UNCAUGHT EXCEPTION:', {
    error: error.message,
    stack: error.stack
  });
  
  // Don't exit the process, just log the error
});

// Log unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
  logWithDetails('UNHANDLED PROMISE REJECTION:', {
    reason: reason ? (reason.stack || reason.message || reason) : 'Unknown reason'
  });
});

// Log process info on startup
logWithDetails('WebSocket server initialization process complete', {
  pid: process.pid,
  node: process.version,
  memoryUsage: process.memoryUsage(),
  uptime: process.uptime()
});